%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import warnings
import seaborn as sns
plt.style.use('seaborn')
sns.set_palette('cubehelix')
plt.rcParams['figure.figsize'] = [8, 4.5]
plt.rcParams['figure.dpi'] = 300
warnings.simplefilter(action='ignore', category=FutureWarning)
Install packages
import yfinance as yf
import numpy as np
import pandas as pd
import pyfolio as pf
Set up parameters
RISKY_ASSETS = ['AAPL', 'TM', 'MCD', 'MS', 'WMT']
START_DATE = '2014-12-31'
END_DATE = '2019-12-31'
n_assets = len(RISKY_ASSETS)
Download the stock prices from Yahoo Finance
prices_df = yf.download(RISKY_ASSETS,
start = START_DATE,
end = END_DATE,
adjusted = True)
print(f'Downloaded {prices_df.shape[0]} rows of data.')
prices_df['Adj Close'].plot(title = 'Stock prices of the Considered 5 Assets')
Calculate the simple returns
returns = prices_df['Adj Close'].pct_change().dropna()
Define the weights
portfolio_weights = n_assets * [1 / n_assets]
Calculate portfolio returns:
portfolio_returns = pd.Series(np.dot(portfolio_weights, returns.T),
index = returns.index)
Create the tear sheet (simple variant):
pf.create_simple_tear_sheet(portfolio_returns)
Calculate annualized average returns and their standard deviations
N_PORTFOLIOS = 10 ** 5
N_DAYS = 1752
returns_df = prices_df['Adj Close'].pct_change().dropna()
avg_returns = returns_df.mean() * N_DAYS
cov_mat = returns_df.cov() * N_DAYS
returns_df.plot(title = 'Daily returns of the considered assets');
Simulate random portfolio weights
np.random.seed(42)
weights = np.random.random(size=(N_PORTFOLIOS, n_assets))
weights /= np.sum(weights, axis=1)[:, np.newaxis]
Calculate portfolio metrics
portf_rtns = np.dot(weights, avg_returns)
portf_vol = []
for i in range(0, len(weights)):
portf_vol.append(np.sqrt(np.dot(weights[i].T,
np.dot(cov_mat, weights[i]))))
portf_vol = np.array(portf_vol)
portf_sharpe_ratio = portf_rtns / portf_vol
Create a joint DataFrame with all data
portf_results_df = pd.DataFrame({'returns': portf_rtns,
'volatility': portf_vol,
'sharpe_ratio': portf_sharpe_ratio})
Locate the points creating the Efficient Frontier
N_POINTS = 100
portf_vol_ef = []
indices_to_skip = []
portf_rtns_ef = np.linspace(portf_results_df.returns.min(),
portf_results_df.returns.max(),
N_POINTS)
portf_rtns_ef = np.round(portf_rtns_ef, 2)
portf_rtns = np.round(portf_rtns, 2)
for point_index in range(N_POINTS):
if portf_rtns_ef[point_index] not in portf_rtns:
indices_to_skip.append(point_index)
continue
matched_ind = np.where(portf_rtns == portf_rtns_ef[point_index])
portf_vol_ef.append(np.min(portf_vol[matched_ind]))
portf_rtns_ef = np.delete(portf_rtns_ef, indices_to_skip)
Plot the Efficient Frontier
MARKS = ['o', 'X', 'd', '*', '1']
fig, ax = plt.subplots()
portf_results_df.plot(kind = 'scatter', x = 'volatility',
y = 'returns', c = 'sharpe_ratio',
cmap = 'RdYlGn', edgecolors = 'black',
ax = ax)
ax.set(xlabel = 'Volatility',
ylabel = 'Expected Returns',
title = 'Efficient Frontier')
ax.plot(portf_vol_ef, portf_rtns_ef, 'b--')
for asset_index in range(n_assets):
ax.scatter(x = np.sqrt(cov_mat.iloc[asset_index, asset_index]),
y = avg_returns[asset_index],
marker = MARKS[asset_index],
s = 150,
color = 'black',
label = RISKY_ASSETS[asset_index])
ax.legend()
plt.tight_layout()
plt.show()
Calculate the portfolio with max Sharpe Ratio and min Volatility
max_sharpe_ind = np.argmax(portf_results_df.sharpe_ratio)
max_sharpe_portf = portf_results_df.loc[max_sharpe_ind]
min_vol_ind = np.argmin(portf_results_df.volatility)
min_vol_portf = portf_results_df.loc[min_vol_ind]
Provide the specific proportions of each asset as maximizing Sharpe Ratio
print('Maximum Sharpe Ratio portfolio ----')
print('Performance')
for index, value in max_sharpe_portf.items():
print(f'{index}: {100 * value:.2f}% ', end="", flush=True)
print('\nWeights')
for x, y in zip(RISKY_ASSETS, weights[np.argmax(portf_results_df.sharpe_ratio)]):
print(f'{x}: {100*y:.2f}% ', end="", flush=True)
Provide the specific proportions of each asset as minimizing volatility
print('Minimum Volatility portfolio ----')
print('Performance')
for index, value in min_vol_portf.items():
print(f'{index}: {100 * value:.2f}% ', end = "", flush = True)
print('\nWeights')
for x, y in zip(RISKY_ASSETS, weights[np.argmin(portf_results_df.volatility)]):
print(f'{x}: {100*y:.2f}% ', end = "", flush=True)
Plot the Efficient Frontier
fig, ax = plt.subplots()
portf_results_df.plot(kind = 'scatter', x = 'volatility',
y = 'returns', c = 'sharpe_ratio',
cmap = 'RdYlGn', edgecolors = 'black',
ax = ax)
ax.scatter(x = max_sharpe_portf.volatility,
y = max_sharpe_portf.returns,
c = 'black', marker = '*',
s = 200, label = 'Max Sharpe Ratio')
ax.scatter(x = min_vol_portf.volatility,
y = min_vol_portf.returns,
c = 'black', marker = 'P',
s = 200, label = 'Minimum Volatility')
ax.set(xlabel = 'Volatility', ylabel = 'Expected Returns',
title = 'Efficient Frontier')
ax.legend()
plt.tight_layout()
plt.show()